summaryrefslogtreecommitdiffstats
path: root/src/ui.c
blob: d7feb7e98a8296a4958944febca7be149182b93a (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
#include <stdlib.h>
#include <glib.h>
#include <glib/gprintf.h>
#include <gtk/gtk.h>
#include <string.h>
unsigned char dc_ui_def_u[] = {
#include <ui.xxd>
};
char * dc_ui_def = (char *) dc_ui_def_u;
#define DC_UI_SET_STATUS(b, s) gtk_label_set_text(GTK_LABEL(gtk_builder_get_object(b, "dc_main_status")), s)
#define DC_UI_GTK /* indicating that the default ui will be used */
#include <h.c>
G_DEFINE_AUTOPTR_CLEANUP_FUNC(gchar, g_free)
struct dc_ui_data {
	GtkBuilder * b;
	GKeyFile * k;
	struct dc_client * c;
};
/*
	# configuration file - loaded at startup, saved at exit, comments persist - description:
	[discord.c]
	multiline = true|false
	login = string
	password = string
	strftime = format string
*/
void dc_ui_spawn_message (struct dc_message * m, struct dc_ui_data * d) { /* !m to clear messages */
	size_t i = 0;
	GtkWidget * b;
	GtkWidget * w, * w2;
#define DC_USMTL 32
	char t[DC_USMTL];
	char * c;
	GtkGrid * g = GTK_GRID(gtk_builder_get_object(d->b, "dc_main_messages"));
	if (!m) {
		while (gtk_grid_get_child_at(g, 0, 0))
			gtk_grid_remove_row(g, 0);
		/* struct dc_channel * c = m->channel->guild->channel; */ /* XXX: I wrote this line of code but then I wen't to sleep and I can't remember what should I do here with the channel :shrug: */
		return;
	}
	while ((w = gtk_grid_get_child_at(g, 0, i))) { /* now we get the index BEFORE which message will be placed */
		struct dc_message * before, * after;
		before = (struct dc_message *) g_object_get_data(G_OBJECT(w), "message"); /* this literally mustn't and can't be NULL */
		if ((w2 = gtk_grid_get_child_at(g, 0, i+1)))
			after = (struct dc_message *) g_object_get_data(G_OBJECT(w2), "message"); /* same here */
		else { /* there is nothing after, message is new */
			i++; /* BEFORE WHICH */
			break;
		}
		if (m->time <= before->time) /* we've found a spot at the top of the grid */
			break;
		if (m->time >= before->time && m->time <= after->time) { /* we've found a spot between two messages */
			i++; /* SAME HERE. if there are no messages already, while will fail immediatley and i will remain 0 */
			break;
		}
		i++;
	}
	gtk_grid_insert_row(g, i);
	/* gtk_grid_insert_column(g, i); */ /* useless, we already *HAVE* columns */
	b = gtk_box_new(GTK_ORIENTATION_VERTICAL, 0 /* spacing pixels */);
	snprintf(t, DC_USMTL, "%s#%04d", m->user->username,  m->user->discriminator);
	gtk_container_add(GTK_CONTAINER(b), gtk_label_new(t));
	/* TODO: implement parsing markup here: bold, italic, underline; REMOVE < character; implement tags, timestamps, channels and spoilers with GTK ahrefs */
	c = g_key_file_get_string(d->k, "discord.c", "strftime", NULL);
	strftime(t, DC_USMTL, c ? strcmp(c, "") ? c : "%c" : "%c", localtime(&m->time)); /* singlethreaded only */
	free(c);
	gtk_container_add(GTK_CONTAINER(b), gtk_label_new(t));
	g_object_set_data(G_OBJECT(b), "message", m);
	gtk_grid_attach(g /* grid */, b /* widget to insert */, 0 /* left */, i /* top */, 1 /* width */, 1 /* height */);
	/* if (m->user == m->channel->guild->client->user) */ /* TODO: if I posted the message, make it an editable textview */
	gtk_grid_attach(g, GTK_WIDGET(gtk_label_new(m->message)), 1, i, 1, 1);
	gtk_widget_show_all(GTK_WIDGET(g));
	gtk_widget_show_all(GTK_WIDGET(g));
}
void dc_ui_spawn_channel (struct dc_channel * c /* needs a functional guild or segfaults */, struct dc_ui_data * d) { /* adds a channel to the channel list, !c to clear all entries */
	GtkTreeStore * l = GTK_TREE_STORE(gtk_builder_get_object(d->b, "dc_main_tree")); /* this func is transfer none */
	GtkTreeView * t = GTK_TREE_VIEW(gtk_builder_get_object(d->b, "dc_main_channels"));
	GtkTreeIter i;
	if (!c) {
		gtk_tree_store_clear(l);
		for (size_t i = 0; i < d->c->guilds_length; i++) {
			d->c->guilds[i]->is_iter = FALSE;
			struct dc_channel * ch = d->c->guilds[i]->channel;
			while (ch) {
				ch->is_iter = FALSE;
				ch = ch->next;
			}
		}
	}
	if (!c->guild->is_iter) { /* we don't have this guild already rendered */
		gtk_tree_store_insert(l, &i, NULL, -1 /* row position */);
		gtk_tree_store_set(l, &i, 0, c->guild->name, -1);
		gtk_tree_store_set(l, &i, 1, c->guild, -1);
		memcpy(&c->guild->iter, &i, sizeof(GtkTreeIter));
		c->guild->is_iter = TRUE;
	}
	gtk_tree_store_insert(l, &i, &c->guild->iter, -1); /* for this to work, iter must not be same as parent! */
	gtk_tree_store_set(l, &i, 0, c->name, -1); /* TODO: set hover tooltip for c->topic */
	gtk_tree_store_set(l, &i, 1, c, -1);
	memcpy(&c->iter, &i,  sizeof(GtkTreeIter));
	c->is_iter = TRUE;
	if (!gtk_tree_view_get_column(t, 0))
		gtk_tree_view_append_column(t, gtk_tree_view_column_new_with_attributes("Channel", gtk_cell_renderer_text_new(), "text", 0, NULL));
	gtk_tree_view_expand_all(t);
}
gchar * gtk_text_buffer_get_all_text(GtkTextBuffer * b) {
	GtkTextIter s, e;
	gtk_text_buffer_get_start_iter(b, &s);
	gtk_text_buffer_get_end_iter(b, &e);
	gchar * c = gtk_text_iter_get_text(&s, &e);
	return c; /* must-g_free, transfer-full */
}
G_MODULE_EXPORT void dc_ui_settings_ok (GtkButton * b, struct dc_ui_data * d) {
	g_key_file_set_boolean(d->k, "discord.c", "multiline", gtk_switch_get_active(GTK_SWITCH(gtk_builder_get_object(d->b, "dc_settings_multiline"))));
	g_key_file_set_string(d->k, "discord.c", "login", gtk_entry_get_text(GTK_ENTRY(gtk_builder_get_object(d->b, "dc_settings_login"))));
	g_key_file_set_string(d->k, "discord.c", "password", gtk_entry_get_text(GTK_ENTRY(gtk_builder_get_object(d->b, "dc_settings_password"))));
	g_key_file_set_string(d->k, "discord.c", "strftime", gtk_entry_get_text(GTK_ENTRY(gtk_builder_get_object(d->b, "dc_settings_strftime"))));
	gtk_widget_hide(gtk_widget_get_toplevel(GTK_WIDGET(b)));
}
G_MODULE_EXPORT void dc_ui_inputbox_changed (GtkWidget * i, struct dc_ui_data * d) {
	GtkTextView * t = GTK_TEXT_VIEW(gtk_builder_get_object(d->b, "dc_main_multiline"));
	GtkEntry * e = GTK_ENTRY(gtk_builder_get_object(d->b, "dc_main_singleline"));
	GtkWidget * b = GTK_WIDGET(gtk_builder_get_object(d->b, "dc_main_send"));
	gchar * c = gtk_text_buffer_get_all_text(gtk_text_view_get_buffer(t));
	gtk_widget_set_sensitive(b, c[0] || gtk_entry_get_text(e)[0] ? TRUE : FALSE);
	g_free(c);
}
void dc_ui_inputbox_activate (GtkWidget * a, struct dc_ui_data * d) {
	GtkTextView * t = GTK_TEXT_VIEW(gtk_builder_get_object(d->b, "dc_main_multiline"));
	GtkEntry * e = GTK_ENTRY(gtk_builder_get_object(d->b, "dc_main_singleline"));
	GtkTextBuffer * b = gtk_text_view_get_buffer(t);
	gchar * c = (gchar *) /* DROPPING const HERE!!! TODO: do this more politely with suppressions etc. */ gtk_entry_get_text(e); /* do not free this one */
	if (c[0])
		g_print("entry says: %s\n", c);
	else { /* we need text from the textview, entry is empty */
		c = gtk_text_buffer_get_all_text(b);
		g_print("textview says: %s\n", c);
		a = NULL; /* so we mark the state, if we should free c or not */
	}
	/* do stuff with c */
	if (c[0] == '/') /* handle command */
		switch (c[1]) { /* unlike before the rewrite / can't be escaped, because escaping / is useless. */
			case 'c': /* clear messages, developing debugging TODO: delete before production useless */
			case 'C':
				dc_ui_spawn_message(NULL, d);
				break;
			case 'd': /* debug create message TODO: delete before production useless */
			case 'D': /* /debug unixtimestamp message */
				;
				struct dc_user u;
				u.username = "yeet";
				struct dc_message * m = malloc(sizeof(struct dc_message)); /* memory leak! */
				m->time = atoi((strchr(c, ' ') ? strchr(c, ' ') : c) + 1);
				m->user = &u;
				m->message = "test message";
				dc_ui_spawn_message(m, d);
				break;
			case 't': /* debug add string to the channel tree. TODO: delete before production useless */
			case 'T': /* /tree position string */
				;
				struct dc_channel * c = calloc(1, sizeof(struct dc_channel)); /* memory leak! */
				struct dc_guild * g = calloc(1, sizeof(struct dc_guild));
				c->guild = g;
				c->name = "this is a parent.";
				c->guild->name = "this is a child.";
				dc_ui_spawn_channel(c, d);
				break;
				GtkTreeStore * l = GTK_TREE_STORE(gtk_builder_get_object(d->b, "dc_main_tree"));
				GtkTreeView * t = GTK_TREE_VIEW(gtk_builder_get_object(d->b, "dc_main_channels"));
				GtkTreeIter i;
				GtkTreeIter j;
				gtk_tree_store_insert(l, &i, NULL, 0);
				gtk_tree_store_set(l, &i, 0, "eww", -1);
				gtk_tree_store_insert(l, &j, &i, 0); /* for this to work, iter must not be the same as parent! */
				gtk_tree_store_set(l, &j, 0, "eww", -1);
				if (!gtk_tree_view_get_column(t, 0))
					gtk_tree_view_append_column(t, gtk_tree_view_column_new_with_attributes("Channel", gtk_cell_renderer_text_new(), "text", 0, NULL));
				break;
		}
	else { /* send message to channel */

	}
	/* stop doing stuff with c */
	if (!a)
		g_free(c);
	gtk_text_buffer_set_text(b, "", -1);
	gtk_entry_set_text(e, ""); /* singleline */
}
G_MODULE_EXPORT gboolean dc_ui_multiline_focus (GtkTextView * t, GtkDirectionType d /* pojma nimam, kako ta enum pove a mam fokus al ne... čudno */, gpointer u) { /* not working, there's not placeholder then */
	char * p = "Enter message in this multiline text field or switch to a single line in preferences. Send message with Ctrl+Enter.";
	GtkTextBuffer * b = gtk_text_view_get_buffer(t);
	gchar * c = gtk_text_buffer_get_all_text(b);
	if (gtk_widget_has_focus(GTK_WIDGET(t))) {
		if (!strcmp(p, c))
			gtk_text_buffer_set_text(b, "", -1);
	} else
		if (!c[0])
			gtk_text_buffer_set_text(b, p, -1);
	g_free(c);
	return FALSE; /* to keep executing other handles for signals instead of finishing here. AFAIK, RTFM */
}
G_MODULE_EXPORT void dc_ui_set_multiline (GtkSwitch * a, gboolean s, struct dc_ui_data * d) {
	GtkWidget * t = GTK_WIDGET(gtk_builder_get_object(d->b, "dc_main_multiline"));
	GtkWidget * e = GTK_WIDGET(gtk_builder_get_object(d->b, "dc_main_singleline"));
	gtk_widget_hide(e);
	gtk_widget_hide(t);
	if (s)
		gtk_widget_show(t);
	else
		gtk_widget_show(e);
	/* dc_ui_multiline_focus(GTK_TEXT_VIEW(t), 0, NULL); */ /* NOT WORKING, meh, there will be no placeholder */ /* just so we set the placeholder, the most important part, otherwise the user will not even see the textview on some themes <3 */
}
G_MODULE_EXPORT void dc_ui_spawn_window (GtkToolButton * t, GtkWindow * w) {
	gtk_widget_show_all(GTK_WIDGET(w));
}
G_MODULE_EXPORT gboolean dc_ui_handle_close (GtkButton * b, gpointer u) {
	gtk_widget_hide(gtk_widget_get_toplevel(GTK_WIDGET(b)));
	return TRUE; /* so that it stays non-deleted, main window sould call/be handled with gtk_main_quit */
}
G_MODULE_EXPORT void dc_ui_reveal_password (GtkSwitch * t, gboolean s, GtkEntry * e) {
	gtk_entry_set_visibility(e, s);
}
void dc_ui_activate (GtkApplication * app, struct dc_ui_data * d) {
	GtkWidget * w;
	gchar * s;
	w = GTK_WIDGET(gtk_builder_get_object(d->b, "dc_window_main"));
	gtk_builder_connect_signals(d->b, d);
	/* začetek definicije dodatnih signalov */
	/* g_signal_connect(gtk_builder_get_object(b, "dc_settings_multiline"), "state-set", G_CALLBACK(dc_ui_set_multiline), b); */
	/* konec definicije dodatnih signalov */
	gtk_widget_show_all(w);
	dc_ui_set_multiline(NULL, g_key_file_get_boolean(d->k, "discord.c", "multiline", NULL), d);
	dc_ui_inputbox_changed(NULL, d);
	/* začetek aplikacije konfiguracijskih vrednosti v UI */
	gtk_switch_set_state(GTK_SWITCH(gtk_builder_get_object(d->b, "dc_settings_multiline")), g_key_file_get_boolean(d->k, "discord.c", "multiline", NULL));
	s = g_key_file_get_string(d->k, "discord.c", "login", NULL);
	gtk_entry_set_text(GTK_ENTRY(gtk_builder_get_object(d->b, "dc_settings_login")), s ? s : "");
	g_free(s);
	s = g_key_file_get_string(d->k, "discord.c", "password", NULL);
	gtk_entry_set_text(GTK_ENTRY(gtk_builder_get_object(d->b, "dc_settings_password")), s ? s : "");
	g_free(s);
	/* gtk zdaj požene main loop */
}
int dc_ui (int argc, char ** argv) {
	GtkApplication *app;
	int status;
	struct dc_ui_data d;
	gchar * s;
	gtk_init(&argc, &argv);
	app = gtk_application_new("eu.sijanec.discord.c", G_APPLICATION_FLAGS_NONE);
	g_signal_connect(app, "activate", G_CALLBACK(dc_ui_activate), &d);
	/* pre app run setup */
	d.b = gtk_builder_new_from_string(dc_ui_def, -1);
#define dc_uacf "%s/%sdiscord.c", getenv("XDG_CONFIG_HOME") ? getenv("XDG_CONFIG_HOME") : getenv("HOME") ? getenv("HOME") : ".", getenv("XDG_CONFIG_HOME") ? "" : ".config/" /* as per XDG */
	gchar fn[snprintf(NULL, 0, dc_uacf)]; /* glib has g_get_user_config_dir but naaaaaaaaaaah  */
	sprintf(fn, dc_uacf);
	s = strrchr(fn, '/');
	s[0] = '\0';
	g_mkdir_with_parents(fn, 0700 /* as per XDG */);
	s[0] = '/';
	d.k = g_key_file_new();
	g_key_file_load_from_file(d.k, fn, G_KEY_FILE_KEEP_COMMENTS | G_KEY_FILE_KEEP_TRANSLATIONS, NULL);
	/* app run */
	status = g_application_run(G_APPLICATION(app), argc, argv);
	gtk_main(); /* XXX: NO IDEA why this has to be run.... gtk_main loop should be started by g_application_run, but it doesn't do that FOR SOME REASON. HELP */
	/* post app cleanup */
	g_object_unref(d.b);
	if (!g_key_file_save_to_file(d.k, fn, NULL))
		g_warning("couldn't save config");
	g_key_file_free(d.k);
	/* dc_ui cleanup */
	g_object_unref(app);
	return status;
}